package org.java.documents.jdk.service;

import org.apache.commons.io.IOUtils;
import org.java.commons.vo.Result;
import org.java.documents.jdk.domain.DocumentMember;
import org.java.documents.jdk.domain.MemberException;
import org.java.documents.jdk.domain.MemberParameter;
import org.java.documents.jdk.domain.MemberType;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.stereotype.Service;

import java.io.*;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

@Service
public class DocumentServiceImpl implements DocumentService {


    @Override
    public Result upload(InputStream in, String name, String type, long size) {
//         解压后的根目录（解压后的文件夹）
//        File dir = new File("F:/ideafile/jdk-zip/"+ UUID.randomUUID().toString());
//        1、解压缩文件，而且可能需要递归解压缩
        try (ZipInputStream zipInputStream = new ZipInputStream(in);) {
//            进入下一个条目，可以获取到文件的名字、类型、大小等
//            ZipEntry zipEntry = zipInputStream.getNextEntry();
//            System.out.println(zipEntry.getName());
//            System.out.println(zipEntry.getSize());
//              调用下面的方法解析zip方法
            // 解析文件，如果得到文件的时候，则通过回调来处理文件。回到是一个Consumer类型的对象
            this.parseZipFile(zipInputStream, zipEntry -> {
//                System.out.println(zipEntry.getSize());
//                System.out.println(zipEntry.getCompressedSize());
//                System.out.println(zipEntry.getCrc());
//                System.out.println(zipEntry.getComment());
//                System.out.println(zipEntry.getName());
//                byte [] buffer = new byte[1024];
//                try {
//                    for (int len = zipInputStream.read(buffer);len != -1;
//                        len = zipInputStream.read(buffer)){
//                        String x = new String(buffer,0,len);
//                        System.out.println(x);
//                    }
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
//            存文件
//                File file = new File(dir,zipEntry.getName());
//                if(!file.getParentFile().exists()){
////                    如果上级类目不存在，需要创建一下,mkdirs建立新的子目录
//                    file.getParentFile().mkdirs();
//                }
//                try {
////                    将所有字节从输入流复制到文件。
//                    Files.copy(zipInputStream,file.toPath());
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
//                解析API文档中所有的模块
//                接着处理包
//                最后解析类型、成员变量、方法、构造器
//                把名字和流传过去（解析成员）
//                System.out.println(zipEntry.getName());
                parseMember(zipEntry.getName(), zipInputStream);
            });
//            目录
//           if(zipEntry.isDirectory()){
//           }
//            System.out.println(memberMap);
        } catch (IOException e) {
            e.printStackTrace();
        }
//        2、提取文件内容，为了统一格式
//        3、数据的存储和索引

        return Result.ok("文档上传成功");
    }


    private org.java.documents.jdk.domain.Document document;
    //  创建一个装文档成员的集合，提供查询
    private Map<String, DocumentMember> memberMap = new HashMap<>();

    private void parseMember(String name, ZipInputStream zipInputStream) {
//        System.out.println(name);
////        第一个文件时index.html的文件，它会直接跳转到另外一个文件，所以不需要处理
        if (name.equals("docs/index.html")
//                ||name.equals("allclasses-index.html")
//                ||name.equals("allclasses.html")
//                ||name.equals("allpackages-index.html")
//                ||name.equals("constant-values.html")
//                ||name.equals("deprecated-list.html")
//                ||name.equals("element-list")
//                ||name.equals("help-doc.html")
        ) {
            return;
//            如果文件以png结尾的也不需要处理
//        }else if(name.equals("allclasses-index.html")){
//            return;
        } else if (name.endsWith(".png")) {
            return;
        }

        //        提取module-summary.html的内容
//        endsWith字符串字段由这个值结尾
        if (name.endsWith("module-summary.html")) {
//        模块名
            String moduleName = name.substring(9);
//        indexOf查找字符或者子串第一次出现的地方,0到/结束
            moduleName = moduleName.substring(0, moduleName.indexOf("/"));
//        检查模块的module-summary.html文件是否已处理，如果已处理过则忽略
//        解析模块
            parseModule(moduleName, name, zipInputStream);
        } else if (name.indexOf("docs/api/index-files/") == 0
                || name.indexOf("docs/legal/") == 0) {
            // 索引文件不处理
        } else if (name.endsWith(".zip")
                || name.endsWith(".js")
                || name.endsWith(".css")
                || name.endsWith(".png")
                || name.endsWith(".jpg")
                || name.endsWith(".gif")
                || name.indexOf("/resources/") > 0) {
            // zip、js、css、图片等文件忽略不处理
//            官方规范
        } else if (name.indexOf("docs/specs/") == 0) {
            // 解析各种规范文件，比如JNI、JMX等，用于辅助实现高级功能的
//            package-summary包的说明
        } else if (name.endsWith("package-summary.html")
//                package-tree类（树）的结构关系
                || name.endsWith("package-tree.html")
//                package-use谁用了我这包
                || name.endsWith("package-use.html")) {
            // 都是解析包的说明文件
        } else if (name.indexOf("class-use") > 0) {
            // 解析类被哪些地方使用了
        } else if (name.endsWith("allclasses.html")
                || name.endsWith("deprecated-list.html")
                || name.endsWith("help-doc.html")
                || name.endsWith("overview-tree.html")
                || name.endsWith("allclasses-index.html")
                || name.endsWith("allpackages-index.html")
                || name.endsWith("constant-values.html")
                || name.endsWith("element-list")
                || name.endsWith("serialized-form.html")
                || name.endsWith("overview-summary.html")
                || name.indexOf("/doc-files/") > 0) {
        } else if (name.equals("docs/api/index.html")) {
//            解析index页面
            parseIndex(name, zipInputStream);
        } else {
//            System.out.println("其它文件："+ name);
            // 类文件的解析
            parseClass(name, zipInputStream);
        }

    }

    private void parseIndex(String name, ZipInputStream zipInputStream) {
        org.java.documents.jdk.domain.Document doc = new org.java.documents.jdk.domain.Document();
        this.document = doc;

        try {
            Document document = this.getDocument(zipInputStream);

            Elements titleEle = document.select(".title");
            String title = titleEle.text();

            String description = document.select(".block").text();

            doc.setName(title);
            doc.setSummary(description);
            doc.setDescription(description);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //      解析类文件
    private void parseClass(String name, ZipInputStream zipInputStream) {
        System.out.println(name);
//        得到类名：前面取9位文件夹名，取模块名，加一个1代表“/”，减去5代表“.html”,最后把斜杆换成点才是类名
        String className = name.substring(9);
        className = className.substring(className.indexOf("/") + 1, className.length() - 5);
//        replace把选中的作为目标篇，用新的字符串替换
        className = className.replace("/", ".");

//        类如果已经处理过，则不在判断
        if (memberMap.containsKey(className) && memberMap.get(className).getName() != null) {
            return;
        }
//        否则就是还没处理的，而没处理的，需要处理
        try {
//            获取解压缩的文件
            Document document = this.getDocument(zipInputStream);
//            获取subTirle的a标签
            Elements subTitles = document.select(".subTitle a");
//            模块名为：subTitles中的第一个文本
            String moduleName = subTitles.first().text();
//            包名
            String packageName = subTitles.get(1).text();
            System.out.println("模块名称：" + moduleName);
            System.out.println("包名：" + packageName);

//            select选择器，选择block模块,返回的是Elements（元素）
            Elements elements = document.select(".block");
//            查看详述内容
            String description = elements.text();
//            概述
            String summary;
            if (description.indexOf('.') > 0) {
                summary = description.substring(0, description.indexOf('.'));
            } else {
                summary = description;
            }

            DocumentMember member = getMember(className);
            member.setMemberType(MemberType.CLASS);
            member.setDescription(description);
            member.setSummary(summary);
            member.setName(className);

            memberMap.put(className, member);
//            System.out.println("moduleName:"+memberMap.get(moduleName));
//            memberMap.get(moduleName).getMembers().add((member));
            getMember(moduleName).getMembers().add(member);

//            parseNestedClasses解析嵌套类
//            parseFields解析成员变量
//            parseConstructors解析构造器
//            parseMethods解析方法
            parseNestedClasses(document, member);
            parseFields(document, member);
            parseConstructors(document, member);
            parseMethods(document, member);


        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void parseMethods(Document document, DocumentMember member) {
//        获取a标签
        Element methodSummary = this.getSummaryElement(document, "a[id='method.summary']");
//        System.out.println(methodSummary);
//        把所有的tr找出来
        if (methodSummary == null) {
            return;
        }
        Elements trs = methodSummary.select("tr");
//            System.out.println("tr的size:" + trs.size());
//        第一行是标题不需要，直接从一开始
        for (int i = 1; i < trs.size(); i++) {
            Element tr = trs.get(i);
            Elements tds = tr.children();
//            类型，名称，描述
//            返回值类型，没有a标签，表示基本类型，直接返回类型，有a标签的是个对象
            Element type = tds.get(0);
            Element nameElement = tds.get(1);
            Element summaryElement = tds.get(2);

//获取Modifier and Type修饰符和类型
            DocumentMember dataType = new DocumentMember();
            if (type.select("a").size() > 0) {
                //引用数据类型，并且可以找到对应的类
//                    查找第一个元素：为a的元素
                Element typeElement = type.selectFirst("a");
//                    找到其属性title
                String typeName = typeElement.attr("title");
//                    截取从in开始 加上四个字节 加上一个点 加上元素里面的内容
                typeName = typeName.substring(typeName.indexOf("in") + 4) + "." + typeElement.text();
                dataType = getMember(typeName);
            } else {
                //基本数据类型。或者找不到对应的
                dataType.setName(type.text());
            }

//获取method名称
//                查询到元素名称为memberNameLink的，再找到a标签，找到href属性,
            String name = nameElement.select(".memberNameLink a").attr("href");
//                概述信息
            String summary = summaryElement.text();

//                System.out.println("方法名：" + member.getName()+name);
            DocumentMember method = this.getMember(member.getName() + name);
            method.setName(name);
            method.setSummary(summary);
            method.setMemberType(MemberType.METHOD);
            method.setDataType(dataType);

//              去掉#号
            String methodName = name.substring(1);
//              把URL编码的ASCII转换为正常的字符，如把%5D转换为]；%5B转换为[
            methodName = URLDecoder.decode(methodName, Charset.forName("utf-8"));
//                方法详细
            methodDetails(method, methodName, document);
        }

    }

    private void methodDetails(DocumentMember method, String methodName, Document document) {
//        获取方法详细的a标签的紧邻选择器
        Elements details = document.select("a[id='" + methodName + "']+ul");
        Element ul = details.first();
//        System.out.println(ul);
//        System.out.println(ul.outerHtml());//外部的html
        String methodSignature = ul.select(".methodSignature").text();
        String description = ul.select(".block").text();
        method.setSignature(methodSignature);//签名
        method.setDescription(description);//说明文字
//        System.out.println("属于哪个方法："+ method.getName());
//        System.out.println("方法的定义：" + methodSignature);
//        System.out.println("方法的描述：" + description);

//        处理类型
        methodParameters("dl > dt > span.paramLabel", ul, (parameterName, parameterDescription) -> {
            MemberParameter parameter = new MemberParameter();
            parameter.setDescription(parameterDescription);
            parameter.setName(parameterName);
            parameter.setSummary(parameterDescription);

            method.getMemberParameters().add(parameter);
        });

//        处理异常
        methodParameters("dl > dt > span.throwsLabel", ul, (parameterName, parameterDescription) -> {
            MemberException ex = new MemberException();
            ex.setSummary(parameterDescription);
            ex.setTitle(parameterName);
            ex.setSummary(parameterDescription);

            method.getMemberExceptions().add(ex);
        });
        // TODO 处理参数的类型
        String  signature = ul.select(".methodSignature").html();
        System.out.println("替换前：" + signature);
        signature = signature.replaceAll("&lt;.+&gt;"," ");
        System.out.println("替换后：" + signature);
        String parameters = signature.substring(signature.indexOf("(")+1,signature.indexOf(")"));
        String [] parameterList = parameters.split(",");
        for (int i=0;i<parameterList.length;i++){
            String parameter = parameterList[i];
            //把 HTML的转义实体，替换成空格，并且把行尾、行头的空格去掉
            parameter = parameter.replaceAll("nbsp;" , " ").trim();
            String type ;
            if (parameter.startsWith("<a")){
                //解析a标签里面的title属性和a标签的文字内容
                type = parameter.substring(parameter.indexOf(" in ") + 4);
                String packageName = type.substring(0, type.indexOf("\""));
                type = packageName + "." + type.substring(type.indexOf(">") + 1, type.indexOf("<"));
            }else {
                //没有a标签的，基本数据类型，或者第三方库里面的数据类型
                type = parameter.split(" ")[0];//使用空格隔开，第一个字符串就是数据类型
            }
            DocumentMember parameterType = getMember(type);
            method.getMemberParameters().get(i).setType(parameterType);
        }
        // TODO 处理see also
    }

    private void methodParameters(String selector, Element ul, BiConsumer<String, String>callback){
        //      "dl>dt>span"寻找下级元素（子选择器）；"dl span"（后代选择器）
//       下级元素选择器，使用 > 根据dt里面的span来确定参数列表
        Element paramLabel = ul.select(selector).first();
        if (paramLabel != null) {
            //上级元素（父级）parent
            Element dt = paramLabel.parent();
            //获取dt的元素后面所有的紧邻元素 nextElementSiblings（下一个元素的兄弟姐妹）
            Elements next = dt.nextElementSiblings();

            for (int i = 0; i < next.size(); i++) {
                Element dd = next.get(i);

                if (!dd.tagName().equals("dd")) {
                    break;
                }
//              参数名
                String parameterName= dd.select("code").first().text();
//                详情描述
//              trim:  返回一个字符串，其值为该字符串，去掉所有的前导和尾随空格，其中空格定义为codepoint小于或等于'U+0020'(空格字符)的任何字符。
                //也可以用text(),带html的标签;如果需要把div元素也要拿到，就使用outerHtml,外部的，包含dov
                String parameterDescription = dd.html();
                parameterDescription = parameterDescription.substring(parameterDescription.indexOf("-") + 1).trim();

//                System.out.println("参数名："+parameterName);
//                System.out.println("参数描述："+parameterDescription);
//                MemberParameter parameter = new MemberParameter();
//                parameter.setDescription(parameterDescription);//描述
//                parameter.setName(parameterName);
//                parameter.setSummary(parameterDescription);
//
//                method.getMemberParameters().add(parameter);
                callback.accept(parameterName,parameterDescription);
            }
        }
    }

    private void parseConstructors(Document document, DocumentMember member) {
        Element nestedClassesSummary = this.getSummaryElement(document, "#constructor.summary");
    }

    private void parseFields(Document document, DocumentMember member) {
        Element nestedClassesSummary = this.getSummaryElement(document, "#field.summary");
    }

    private void parseNestedClasses(Document document, DocumentMember member) {
        Element nestedClassesSummary = this.getSummaryElement(document, "#nested.class.summary");
    }

    private Element getSummaryElement(Document document, String selector) {
        // 选择a标签
        Elements nestedClassesA = document.select(selector);
        Element a = nestedClassesA.first();
        if (a == null) {
            return null;
        }
        // 上级元素
        Element li = a.parent();
        // 通过上级元素找到里面的成员概述
        Element ele = li.select(".memberSummary").first();
        return ele;
    }

    private DocumentMember getMember(String name) {
        if (!memberMap.containsKey(name)) {
            DocumentMember member = new DocumentMember();
            member.setMembers(new LinkedList<>());
            memberMap.put(name, member);
        }
        return memberMap.get(name);
    }


    //解析模块的方法
    private void parseModule(String moduleName, String name, ZipInputStream zipInputStream) {

        if (memberMap.containsKey(moduleName) && memberMap.get(moduleName).getName() != null) {
//          在memberMap里面包含了此模块的信息，认为已经处理过
            return;
        }
        System.out.println("文档路径：" + name);
        try {
            Document document = this.getDocument(zipInputStream);
//            select选择器，选择block模块,返回的是Elements（元素）
            Elements elements = document.select(".block");
//          打印Elements里面有多少个，因为它是复数
//            System.out.println(elements.size());

//            查看元素的内容
//            text返回元素里面所有的内容，包阔子元素的
//            String text = elements.text();

//            可以把所有匹配的元素的文本内容，全部返回，包括里面的子元素
//            List<String>text = elements.eachText();

//            第一个匹配的元素的文本内容，包括子元素
//            String text = elements.first().text();
//            System.out.println("元素的内容："+ text);

            String summary = elements.text();
            DocumentMember member = getMember(moduleName);
            member.setMemberType(MemberType.MODULE);
            member.setDescription(summary);
            member.setSummary(summary);
            member.setName(moduleName);

            memberMap.put(moduleName, member);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private Document getDocument(ZipInputStream zipInputStream) throws IOException {
//        读取压缩文件的内容，并且解压缩以后放入ByteArrayOutputStream里面
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        IOUtils.copy(zipInputStream, out);
        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
//        使用jsoup的parse解析html文档，（流，字符集名称，网址的根目录）
        Document document = Jsoup.parse(in, "utf-8", "./");
        return document;
    }

    /**
     * @param zipInputStream 整个ZIP压缩文件
     * @param action         如何处理得到的每个文件（非目录）
     *                       Consumer  函数式接口
     */
    private void parseZipFile(ZipInputStream zipInputStream, Consumer<ZipEntry> action) throws IOException {
        while (true) {
//      进入下一个条目，可以获取到文件的名字、类型、大小等
            ZipEntry zipEntry = zipInputStream.getNextEntry();
            if (zipEntry == null) {
                return;
            }
////      如果是目录
            if (zipEntry.isDirectory()) {
//      处理目录
//            System.out.println(zipEntry.getClass());
//            使用递归处理目录
                this.parseZipFile(zipInputStream, action);
            } else {
//      处理文件
                action.accept(zipEntry);
            }
//       完成当前ZipEntry的处理，进行下一个处理
//       closeEntry处理完这个进入下一个，close结束
            zipInputStream.closeEntry();
        }
    }

}
